package com.zboot.comm.es.service;

import com.alibaba.fastjson.JSONObject;
import com.zboot.comm.web.page.AjaxPageResult;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Component
@Slf4j
public class EsService {

    @Autowired
    private RestHighLevelClient client;

    @SneakyThrows
    public BulkByScrollResponse updateByQuery(UpdateByQueryRequest updateByQueryRequest) {
        BulkByScrollResponse resp = client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);
        return resp;
    }

    /**
     * 创建索引
     *
     * @param index 索引
     * @return
     */
    @SneakyThrows
    public boolean createIndex(String index){
        index = index.toLowerCase();
        if(isIndexExist(index)){
            log.error("创建索引失败，Index [{}] is exits!", index);
            return false;
        }
        //1.创建索引请求
        CreateIndexRequest request = new CreateIndexRequest(index);
        //2.执行客户端请求
        log.info("es创建索引 /{}",index);
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        return response.isAcknowledged();
    }

    /**
     * 删除索引
     *
     * @param index
     * @return
     */
    @SneakyThrows
    public boolean deleteIndex(String index){
        index = index.toLowerCase();
        if(!isIndexExist(index)) {
            log.error("es删除索引失败，Index [{}] is Not exits!", index);
            return false;
        }
        //删除索引请求
        DeleteIndexRequest request = new DeleteIndexRequest(index);
        //执行客户端请求
        log.info("es删除索引 /{}",index);
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        return delete.isAcknowledged();
    }

    /**
     * 判断索引是否存在
     * @param index
     * @return
     */
    @SneakyThrows
    public boolean isIndexExist(String index) {
        index = index.toLowerCase();
        GetIndexRequest request=new GetIndexRequest()
                .indices(index);
        boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);
        return exists;
    }

    /**
     * 数据添加，指定id
     *
     * @param entity 要增加的数据
     * @param index      索引，类似数据库
     * @param id         数据ID, 为null时es随机生成
     * @return
     */
    @SneakyThrows
    public String save(String index, Object entity, String id){
        index = index.toLowerCase();
        //创建请求
        IndexRequest request = new IndexRequest(index);
        //规则 put /test_index/_doc/1
        request.id(id);
        request.timeout(TimeValue.timeValueSeconds(1));
        //将数据放入请求 json
        IndexRequest source = request.source(entity, XContentType.JSON);
        //客户端发送请求
        log.info("es保存数据 /{}, id: {}",index, id);
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        return response.getId();
    }



    /**
     * 数据添加 随机id
     *
     * @param entity 要增加的数据
     * @param index      索引，类似数据库
     * @return
     */
    public String save(String index, Object entity){
        index = index.toLowerCase();
        return save(index, entity, UUID.randomUUID().toString());
    }

    /**
     * 通过ID删除数据
     *
     * @param index 索引，类似数据库
     * @param id    数据ID
     */
    @SneakyThrows
    public void deleteById(String index, String id){
        index = index.toLowerCase();
        //删除请求
        DeleteRequest request = new DeleteRequest(index, id);
        //执行客户端请求
        log.info("es删除数据 /{}, id: {}", index, id);
        client.delete(request, RequestOptions.DEFAULT);
    }

    /**
     * 通过ID 更新数据
     *
     * @param entity     要增加的数据
     * @param index      索引，类似数据库
     * @param id         数据ID
     * @return
     */
    @SneakyThrows
    public void updateDataById(String index, Object entity, String id){
        index = index.toLowerCase();
        //更新请求
        UpdateRequest update = new UpdateRequest(index, id);
        //保证数据实时更新
        //update.setRefreshPolicy("wait_for");
        update.timeout("1s");
        update.doc(entity, XContentType.JSON);
        //执行更新请求
        log.info("es更新数据 /{}, id: {}", index, id);
        UpdateResponse update1 = client.update(update, RequestOptions.DEFAULT);
    }

    /**
     * 通过ID 更新数据,保证实时性
     *
     * @param entity     要增加的数据
     * @param index      索引，类似数据库
     * @param id         数据ID
     * @return
     */
    @SneakyThrows
    public void updateDataByIdNoRealTime(String index, Object entity, String id){
        index = index.toLowerCase();
        //更新请求
        UpdateRequest update = new UpdateRequest(index, id);
        //保证数据实时更新
        update.setRefreshPolicy("wait_for");
        update.timeout("1s");
        update.doc(entity, XContentType.JSON);
        //执行更新请求
        log.info("es更新数据（实时） /{}, id: {}", index, id);
        UpdateResponse update1 = client.update(update, RequestOptions.DEFAULT);
    }

    /**
     * 通过ID获取数据
     *
     * @param index  索引，类似数据库
     * @param id     数据ID
     * @return
     */
    @SneakyThrows
    public <T> T getById(String index, String id, Class<T> clazz){
        index = index.toLowerCase();
        GetRequest request = new GetRequest(index, id);
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        log.info("es查询数据 /{}/{}", index, id);
        return JSONObject.parseObject(response.getSourceAsString(), clazz);
    }

    /**
     * 通过ID判断文档是否存在
     * @param index  索引，类似数据库
     * @param id     数据ID
     * @return
     */
    @SneakyThrows
    public  boolean existsById(String index, String id){
        index = index.toLowerCase();
        GetRequest request = new GetRequest(index, id);
        //不获取返回的_source的上下文
        request.fetchSourceContext(new FetchSourceContext(false));
        request.storedFields("_none_");
        return client.exists(request, RequestOptions.DEFAULT);
    }

    /**
     * 查询并分页
     * @param index          索引名称
     * @param ssb            查询条件
     * @param pageNum        页数，从1开始
     * @param pageSize       每页条数
     * @return
     */
    public <T> AjaxPageResult<T> listPage(String index, SearchSourceBuilder ssb, Class<T> clazz, Integer pageNum, Integer pageSize){
        index = index.toLowerCase();
        SearchResponse response = this.search(index, ssb, pageNum, pageSize);
        AjaxPageResult<T> ajaxPageResult = new AjaxPageResult<>();
        if (response.status().getStatus() == 200) {
            ajaxPageResult.setData(parseResponse(response, clazz));
            ajaxPageResult.setTotal(response.getHits().getTotalHits().value);
            // 解析对象
            return ajaxPageResult;
        }
        return ajaxPageResult;
    }

    public <T> AjaxPageResult<T> listPageWithHighlight(String index, SearchSourceBuilder ssb, Class<T> clazz, Integer pageNum, Integer pageSize){
        index = index.toLowerCase();
        SearchResponse response = this.search(index, ssb, pageNum, pageSize);
        AjaxPageResult<T> ajaxPageResult = new AjaxPageResult<>();
        if (response.status().getStatus() == 200) {
            ajaxPageResult.setData(highLight(response, clazz));
            ajaxPageResult.setTotal(response.getHits().getTotalHits().value);
            // 解析对象
            return ajaxPageResult;
        }
        return ajaxPageResult;
    }

    /**
     * 查询列表
     * @param index        索引名称
     * @param ssb          查询条件
     * @return
     */
    public <T> List<T> list(String index, SearchSourceBuilder ssb, Class<T> clazz){
        index = index.toLowerCase();
        SearchResponse response = this.search(index, ssb, null, null);
        if (response.status().getStatus() == 200) {
            return parseResponse(response, clazz);
        }
        return null;
    }

    public <T> List<T> listWithHighlight(String index, SearchSourceBuilder ssb, Class<T> clazz){
        index = index.toLowerCase();
        SearchResponse response = this.search(index, ssb, null, null);
        if (response.status().getStatus() == 200) {
            return highLight(response, clazz);
        }
        return null;
    }

    @SneakyThrows
    private SearchResponse search(String index, SearchSourceBuilder ssb, Integer pageNum, Integer pageSize){
        SearchRequest request = new SearchRequest(index);
        if(pageNum!=null && pageSize!=null) {
            pageNum = (pageNum-1)*pageSize;
            //设置确定结果要从哪个索引开始搜索的from选项，默认为0
            ssb.from(pageNum);
            ssb.size(pageSize);
        }
        //不返回源数据。只有条数之类的数据。
        //builder.fetchSource(false);
        request.source(ssb);
        log.info("es查询 /{}\r\n{}", index, ssb.toString());
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return response;
    }

    /**
     * 高亮结果集 特殊处理
     * map转对象 JSONObject.parseObject(JSONObject.toJSONString(map), Content.class)
     * @param searchResponse
     */
    private <T> List<T> highLight(SearchResponse searchResponse, Class<T> clazz) {
        //解析结果
        List<T> list = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();//数据
            Map<String, HighlightField> high = hit.getHighlightFields();//高亮字段
            high.forEach((key, field) -> {//解析高亮字段,将原来的字段换为高亮字段
                if (field!=null){
                    Text[] texts = field.fragments();
                    String nTitle="";
                    for (Text text : texts) {
                        nTitle+=text;
                    }
                    //替换
                    sourceAsMap.put(key, nTitle);
                }
            });
            list.add(JSONObject.parseObject(JSONObject.toJSONString(sourceAsMap), clazz));
        }
        return list;
    }

    private <T> List<T> parseResponse(SearchResponse response, Class<T> clazz) {
        List<T> list = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            list.add(JSONObject.parseObject(hit.getSourceAsString(), clazz));
        }
        return list;
    }
}
